﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;

namespace DocumentAnalyzer
{
	public class Analyzer
	{
		public static int MinimumTextLineLength { get { return _MinimumTextLineLength; } set { _MinimumTextLineLength = value; } }
		public static int PossibleLineMargine { get { return _possibleLineMargine; } set { _possibleLineMargine = value; } }

		private static int _MinimumTextLineLength;
		private static int _possibleLineMargine;

		public static Bitmap ProcessFile(string _filename)
		{
			Bitmap bmp = new Bitmap(_filename);

			Report("Converting to black/white image");
			bmp = ConvertToBlackAndWhite(bmp);
			Report("Done");
			return bmp;
		}

		private static Bitmap ConvertToBlackAndWhite(Bitmap bmp)
		{
			for (int h = 0; h < bmp.Height; h++)
			{
				for (int w = 0; w < bmp.Width; w++)
				{
					var px = bmp.GetPixel(w, h);
					if (px.GetBrightness() > Color.Gray.GetBrightness()) bmp.SetPixel(w, h, Color.White);
					else bmp.SetPixel(w, h, Color.Black);
				}
			}

			return bmp;
		}

		public static bool[][] FillPixelMatrix(Bitmap bmp)
		{
			if (bmp == null) return null;

			var mx = new bool[bmp.Width][];
			for (int i = 0; i < mx.Length; i++) mx[i] = new bool[bmp.Height];

			for (int h = 0; h < bmp.Height; h++)
			{
				for (int w = 0; w < bmp.Width; w++)
				{
					var px = bmp.GetPixel(w, h);
					mx[w][h] = !(px.GetBrightness() > Color.Gray.GetBrightness());
				}
			}
			return mx;
		}

		private static Histogram CreateHistogram(bool[][] pixelMatrix, int xStart, int xEnd)
		{
			if
				(pixelMatrix == null || xStart > pixelMatrix.Length || xStart > xEnd || xEnd > pixelMatrix.Length)
				return null;

			var hist = new Histogram(pixelMatrix[0].Length, xStart, xEnd);
			for (int i = 0; i < hist.Length; i++) hist[i] = 0;
			for (int y = 0; y < pixelMatrix[0].Length; y++)
			{
				for (int x = xStart; x < xEnd; x++)
				{
					if (pixelMatrix[x][y])
					{

						hist[y]++;
					}
				}
			}


			return hist;
		}

		public static Histogram[] CreateHistograms(bool[][] pixelMatrix, int divisions)
		{

			Histogram[] ret;
			int len;
			if (pixelMatrix == null || divisions > pixelMatrix.Length) return null;

			ret = new Histogram[divisions];
			len = (int)pixelMatrix.Length / divisions;
			for (int i = 0; i < divisions; i++)
			{

				ret[i] = CreateHistogram(pixelMatrix, i * len, (i + 1) * len);
			}

			return ret;
		}

		private static Rectangle GetTextArea(Line[][] lines)
		{
			int 
				xMin=int.MaxValue, 
				xMax=int.MinValue, 
				yMin=int.MaxValue, 
				yMax=int.MinValue;
			foreach (Line[] _lines in lines)
			{
				foreach (Line l in _lines)
				{
					if (l.X_Start < xMin) xMin = l.X_Start;
					if (l.X_End > xMax) xMax = l.X_End;
					if (l.Y_Start < yMin) yMin = l.Y_Start;
					if (l.Y_End > yMax) yMax = l.Y_End;
				}
			}
			return new Rectangle(xMin, yMin, xMax - xMin, yMax - yMin);
		}

		public static TextLine[] AnalyzeLines(Line[][] lines)
		{
			List<Line> addedLines = new List<Line>();
			List<TextLine> textLines = new List<TextLine>();
			TextLine tmp;

			//var rec = GetTextArea(lines);

			for (int div = 0; div < lines.Length; div++)
			{
				foreach (Line line in lines[div])
				{
					Line lastLine;
					if (addedLines.Contains(line)) continue;
					tmp = new TextLine();
					tmp.Add(line);
					addedLines.Add(line);
					lastLine = line;
					for (int forward = div + 1; forward < lines.Length; forward++)
					{
						Line closest = null;
						int closestRating = -1;
						foreach (Line inner in lines[forward])
						{
							if (addedLines.Contains(inner)) continue;
							var rating = lastLine.NextTo(inner);
							if (rating > closestRating)
							{
								closestRating = rating;
								closest = inner;
							}
						}
						if (closestRating > 0)
						{
							lastLine = closest;
							addedLines.Add(closest);
							tmp.Add(closest);
						}

					}
					if (tmp.Length > _MinimumTextLineLength) textLines.Add(tmp);
				}
			}

			return textLines.ToArray();
		}

		public static Line[][] FindPossibleLines(Histogram[] histograms)
		{
			List<Line[]> linesList = new List<Line[]>();
			for (int i = 0; i < histograms.Length; i++)
			{
				linesList.Add(GetLines(histograms[i]));
			}
			return linesList.ToArray();
		}

		public static void SortTextLines(ref TextLine[] textLines)
		{
			List<TextLine> sorted = new List<TextLine>(textLines);
			sorted.Sort();
			textLines = sorted.ToArray();
		}

		private static Line[] GetLines(Histogram histogram)
		{
			int possibleInLineMargine;
			int lineMargine = 0;
			int lineStart = 0, lineEnd = 0;
			bool sameLine = false;
			int errorMargin = 0;
			List<Line> lines = new List<Line>();
			if (histogram == null) return null;

			possibleInLineMargine = _possibleLineMargine;

			for (int line = 0; line < histogram.Length; line++)
			{
				if (histogram[line] <= errorMargin)
				{
					lineMargine++;
					if (sameLine && lineMargine > possibleInLineMargine)
					{
						lines.Add(new Line(histogram.X_Start, histogram.X_End, lineStart, lineEnd));
						sameLine = false;
					}
				}
				else
				{
					lineMargine = 0;
					if (!sameLine)
					{
						lineStart = line;
						sameLine = true;
					}
					else
					{
						lineEnd = line;
					}
				}
			}

			return lines.ToArray();
		}

		private static void Report(string p)
		{
			
		}

		/*public static int[] Transitions(bool[][] pixelMatrix, Point p, int[] lengthsToCheck)
		{
			List<int> lToCheck = new List<int>(lengthsToCheck);
			lToCheck.Sort();
			List<int> trans = new List<int>();
			bool last = pixelMatrix[p.X][p.Y];
			int max = lToCheck.Max();
			int l = 0;

			for (int i = p.X + 1; i < max + p.X && i < pixelMatrix.Length; i++)
			{
				if (last != pixelMatrix[i][p.Y]) { trans++; last = !last; }
			}
			return trans.ToArray();
		}*/

		public static int Transitions(bool[][] pixelMatrix, Point p, int lengthToCheck)
		{
			int trans = 0;
			bool last = pixelMatrix[p.X][p.Y];
			for (int i = p.X+1; i < lengthToCheck + p.X && i < pixelMatrix.Length; i++)
			{
				if (last != pixelMatrix[i][p.Y]) { trans++; last = !last; }
			}
			return trans;
		}
		public static int Transitions(bool [][] pixelMatrix, int x, int y, int lengthToCheck)
		{ return Transitions(pixelMatrix, new Point(x, y), lengthToCheck); }
	}

	public class Line
	{
		public static int MargineX = 25;
		public static int MargineY = 15;
		public int Length { get { return X_End - X_Start; } }
		public int Width { get { return Y_End - Y_Start; } }
		public int Y_Start, Y_End;
		public int X_Start, X_End;
		public Line(int xStart, int xEnd, int yStart, int yEnd)
		{
			X_Start = xStart;
			Y_End = yEnd;
			Y_Start = yStart;
			X_End = xEnd;
		}
		/*public bool NextTo(Line other)
		{
			if (X_End + MargineX >= other.X_Start)
				if (Between(other.Y_Start, Y_Start + MargineY, Y_Start-MargineY)
					|| Between(other.Y_End, Y_End + MargineY, Y_End-MargineY)) return true;
			return false;
		}*/

		public int NextTo(Line other)
		{
			if (X_End + MargineX >= other.X_Start)
			{
				if (Between(other.Y_Start, Y_Start + MargineY, Y_Start - MargineY)
					&& Between(other.Y_End, Y_End + MargineY, Y_End - MargineY)) return 2;
				if (Between(other.Y_Start, Y_Start + MargineY, Y_Start - MargineY)
					|| Between(other.Y_End, Y_End + MargineY, Y_End - MargineY)) return 1;
			}
			return 0;
		}

		private bool Between(int num, int upper, int lower)
		{
			return (num >= lower && num <= upper);
		}
	}

	public class TextLine : IComparable<TextLine>
	{
		public static int LineSaveMargine = 5;
		private int _length;
		private List<Line> _lines;
		public TextLine() { _lines = new List<Line>(); }
		public void Add(Line line) { _lines.Add(line); _length += line.Length; }
		public Line this[int lineNumber] { get { return _lines[lineNumber]; } }
		public TextLine(Line[] lines)
		{
			_lines = new List<Line>();
			foreach (Line l in lines)
			{
				this.Add(l);
			}
		}
		public int Length { get { return _length; } }
		public int NumberOfLines { get { return _lines.Count; } }
		public int HorizntalStartOfLine { get { return _lines[0].Y_Start; } }
		public int VerticalStartOfLine { get { return _lines[0].X_Start; } }

		public string SaveToFile(string filename, Bitmap originBitmap)
		{
			try
			{
				int w, minY, maxY, h, minX, maxX;

				minX = _lines[0].X_Start;
				maxX = _lines[0].X_End;
				minY = _lines[0].Y_Start;
				maxY = _lines[0].Y_End;
				foreach (Line l in _lines)
				{
					if (l.Y_Start < minY) minY = l.Y_Start;
					if (l.Y_End > maxY) maxY = l.Y_End;

					if (l.X_Start < minX) minX = l.X_Start;
					if (l.X_End > maxX) maxX = l.X_End;
				}
				w = maxX - minX + 1;
				h = maxY - minY + 1+2*LineSaveMargine;
				Bitmap bmp = originBitmap;
				Bitmap outBmp = new Bitmap(w, h);
				foreach (Line l in _lines)
				{
					for (int x = l.X_Start; x < l.X_End + 1; x++)
						for (int y = l.Y_Start-LineSaveMargine; y < l.Y_End + 1+LineSaveMargine; y++)
						{
							try
							{
								outBmp.SetPixel(x - minX, y - minY-LineSaveMargine, bmp.GetPixel(x, y));
							}
							catch { }
						}
				}
				outBmp.Save(filename);
				return string.Empty;
			}
			catch (Exception ex) { return ex.Message; }
		}

		#region IComparable<TextLine> Members

		public int CompareTo(TextLine other)
		{
			return (HorizntalStartOfLine.CompareTo(other.HorizntalStartOfLine));
		}

		#endregion
	}

	public class Histogram
	{
		private int[] Data;
		public int X_Start, X_End;
		public int this[int loc] { get { return Data[loc]; } set { Data[loc] = value; } }
		public Histogram(int size, int xStart, int xEnd) { Data = new int[size]; X_Start = xStart; X_End = xEnd; }
		public int Length { get { return Data.Length; } }
	}
}
